package eztools

import (
	"sort"

	"golang.org/x/exp/constraints"
)

type pair[K constraints.Ordered, V comparable] struct {
	key K
	val V
}

// Pairs contains key-value pairs.
// Key needs to be orderable so that Sort() works
// Value needs to be comparable so that FindVal() works
type Pairs[K constraints.Ordered, V comparable] struct {
	next   int
	sorted bool
	pair   []pair[K, V]
}

// Rewind used after Add() to reset and before GetNMove() to the first item
func (ps *Pairs[K, V]) Rewind() {
	ps.next = 0
}

// Add appends values to the collection,
//   moving cursor to the end
func (ps *Pairs[K, V]) Add(key K, val V) {
	if ps.pair == nil || len(ps.pair) < 1 {
		ps.sorted = true
	} else {
		ps.sorted = false
	}
	ps.pair = append(ps.pair, pair[K, V]{key, val})
	ps.next = ps.Len()
}

// AddNSort appends values to the collection and sorts it,
//   moving cursor to the end
func (ps *Pairs[K, V]) AddNSort(key K, val V) {
	ps.next = ps.Len() + 1
	if ps.pair != nil && len(ps.pair) > 0 && ps.IsSorted() {
		i := sort.Search(len(ps.pair), func(ind int) bool {
			return ps.pair[ind].key >= key
		})
		ps.pair = append(ps.pair, pair[K, V]{})
		copy(ps.pair[i+1:], ps.pair[i:])
		ps.pair[i] = pair[K, V]{key: key, val: val}
		return
	}
	ps.pair = append(ps.pair, pair[K, V]{key, val})
	ps.Sort()
}

// Next gets the current item in the collection and moves to the next
//   call Rewind() after Add() to reset to and get the first item
// Return value:
//	ErrOutOfBound if collection is exhausted
func (ps *Pairs[K, V]) GetNMove() (rK K, rV V, err error) {
	if ps.pair == nil || ps.next >= len(ps.pair) || len(ps.pair) < 1 {
		return rK, rV, ErrOutOfBound
	}
	defer func() {
		ps.next++
	}()
	return ps.pair[ps.next].key, ps.pair[ps.next].val, nil
}

// Len gets size of the collection
func (ps Pairs[K, V]) Len() int {
	return len(ps.pair)
}

// Get get values of an ellement in the collection
// cursor moves to the next of it
// Return value:
//	ErrOutOfBound if collection is empty or index is not valid
func (ps *Pairs[K, V]) Get(index int) (rK K, rV V, err error) {
	if ps.pair == nil || index >= len(ps.pair) || index < 0 {
		return rK, rV, ErrOutOfBound
	}
	ps.next = index + 1
	return ps.pair[index].key, ps.pair[index].val, nil
}

// Set sets values of an ellement in the collection
// cursor moves to the next of it
// this makes the collection unsorted
// Return value:
//	ErrOutOfBound if collection is empty or index is not valid
func (ps *Pairs[K, V]) Set(index int, key K, val V) error {
	if ps.pair == nil || index >= len(ps.pair) || index < 0 {
		return ErrOutOfBound
	}
	ps.pair[index].key, ps.pair[index].val = key, val
	ps.next = index + 1
	ps.sorted = false
	return nil
}

// SetPrev sets values of previous ellement in the collection,
//   after calling GetNMove(). cursor not moved
// this makes the collection unsorted
// Return value:
//	ErrOutOfBound if collection is empty
func (ps *Pairs[K, V]) SetPrev(key K, val V) error {
	if ps.next < 1 {
		return ErrOutOfBound
	}
	ps.next++ // 'cause Set() moves cursor
	return ps.Set(ps.next-2, key, val)
}

// FindId find the first value whose **key** matches input
//  moving cursor to the next of it
// Return value:
//	ErrOutOfBound if collection is empty
//	ErrNoValidResults when none found
func (ps *Pairs[K, V]) FindKey(s K) (ret V, err error) {
	if ps.pair == nil {
		return ret, ErrOutOfBound
	}
	if ps.sorted {
		sort.Search(len(ps.pair), func(ind int) bool {
			return ps.pair[ind].key >= s
		})
	}
	err = ErrNoValidResults
	for i, v := range ps.pair {
		if v.key == s {
			ps.next = i + 1
			return v.val, nil
		}
	}
	ps.next = ps.Len()
	return
}

// FindStr find the first key whose **value** matches input
//  moving cursor to the next of it
// Return value:
//	ErrOutOfBound if collection is empty
//	ErrNoValidResults when none found
func (ps *Pairs[K, V]) FindVal(s V) (ret K, err error) {
	if ps.pair == nil {
		return ret, ErrOutOfBound
	}
	err = ErrNoValidResults
	for i, v := range ps.pair {
		if v.val == s {
			ps.next = i + 1
			return v.key, nil
		}
	}
	ps.next = ps.Len()
	return
}

// Sort sorts the collection by key and Rewind()
func (ps *Pairs[K, V]) Sort() {
	defer func() {
		ps.sorted = true
	}()
	if ps.sorted || ps.pair == nil || len(ps.pair) < 2 {
		return
	}
	sort.Slice(ps.pair, func(i, j int) bool {
		return ps.pair[i].key < ps.pair[j].key
	})
	ps.Rewind()
}

func (ps Pairs[K, V]) IsSorted() bool {
	if ps.pair == nil || len(ps.pair) < 2 {
		ps.sorted = true
	}
	return ps.sorted
}
